import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.containsString;
...
assertThat("Hello world", containsString("world"));Qui suis je ?
N’hésitez pas à interrompre ou à intervenir
Merci de ne pas faire de bruit :)
Comment ? Un questionnaire à choix multiples
Quand ? La dernière heure avec Richard Liot
Tester ce n’est pas que vérifier que son application marche!
C’est savoir rapidement quand l’application ne marche plus
où dans le code
et pourquoi
Construire sa couverture de tests = construire ses TNR (Tests de Non Régression)
Impact sur la conception
Modularité & testabilité
La méthodologie eXtreme Programming est une méthode de gestion de projet qui applique à l’extrême les principes de ceux des méthodes Agiles.
on se concentre sur les besoins du client ;
mise en place d’un développement itératif (sprints courts de 2/3 semaines) et de l’intégration continue.
La méthode XP s’appuie sur :
une forte réactivité au changement des besoins du client ;
un travail d’équipe ;
la qualité du travail fourni ;
la qualité des tests effectués au plus tôt.
Il s’agit d’une technique de conception où le programmeur écrit d’abord le test avant de produire le moindre code.
Le développeur écrit ensuite le code pour que le test passe.
Une fois son test finalisé, il pourra être libre de refactorer autant qu’il le souhaite jusqu’à obtenir un code « propre ».
C’est une idée simple mais complexe à mettre en oeuvre.
Courbe d’apprentissage plus lente de prime abord.
Vérifier la bonne compréhension des fonctionnalités
Meilleure couverture de tests automatisés
Facilité d’écriture des tests avant le code « métier »
Ils servent à promouvoir et vérifier la qualité et la fiabilité du code
Enfin, jusqu’Ã une certaine limite !!
Ecriture d’un test pour une fonctionnalité
Le test est « failed »
Codage de la fonctionnalité minimale
Vérification du cas passant
Répéter l’opération en enrichissant la fonctionnalité en refactorisant
Red / Green / Refactor
Les tests unitaires consistent à tester individuellement les composants d’une application.
On pourra ainsi valider la qualité du code et les performances d’un module.
Ces tests sont exécutés pour valider l’intégration des différents modules entre eux et dans leur environnement d’exploitation définitif.
Ils permettront de mettre en évidence des problèmes d’interfaces entre différents programmes.
Ces tests ont pour but de vérifier la conformité de l’application développée avec le cahier des charges initial.
Ils sont donc basés sur les spécifications fonctionnelles et techniques.
L’écriture de tests fonctionnels automatisés représente un effort important.
Que pouvez-vous accepter pour valider une fonctionnalité ?
Conformité des fonctionnalités demandées.
Les temps de réponses sont-ils corrects (chargement d’une page HTML, réponse d’une API, …) ?
Ce sont des tests permettant de mesurer les temps de réponses du système en fonction des sollicitations.
Les tests de charge simulent un nombre prédéfini d’utilisateurs en simultané pour mesurer le dimensionnement de l’infrastructure nécessaire (serveurs, bande passante sur le réseau, …)
Les tests de performance permettent de récupérer des métriques (temps de réponses, percentile)
Les tests en « boite noire » consistent à examiner uniquement les fonctionnalités d’une application.
Les tests en « boîte blanche » consistent à examiner le fonctionnement d’une application et sa structure interne, ses processus, plutôt que ses fonctionnalités.
Les tests en « boîte grise » compilent ces deux précédentes approches : ils éprouvent à la fois les fonctionnalités et le fonctionnement d’un système.
En méthode « boîte noire », on vérifie que la voiture fonctionne en allumant les lumières, en klaxonnant et en tournant la clé pour que le moteur s’allume. Si tout se passe comme prévu, la voiture fonctionne.
En méthode « boîte blanche », on emmène la voiture chez le garagiste, qui regarde le moteur ainsi que toutes les autres parties (mécaniques comme électriques) de la voiture. Si elle est en bon état, elle fonctionne.
En méthode « boîte grise », on emmène la voiture chez le garagiste, et en tournant la clé dans la serrure, on vérifie que le moteur s’allume, et le garagiste observe en même temps le moteur pour s’assurer qu’il démarre bien selon le bon processus.
JUnit, TestNG (java)
Jasmine, Karma, Mocha (javascript)
nUnit (.Net)
SimpleTest (PHP)
dUnit (Delphi)
cppUnit (C++) etc..
Mockito, EasyMock, PowerMock (java)
Sinon JS, Jest, Rhino Mocks (javascript)
TypeMock, Moq (.Net)
JMeter, Gatling, Taurus, Locust
Cobertura, JaCoCo (java)
Coverage.py (python)
Bullseye Coverage (C++, C)
NCover, dotCover (.Net)
Tests manuels (SoapUI, Postman)
Tests d’API (frameworks REST Assured, Karate)
Outils de tests de sécurité
type SAST (Source Code Analysis Tools)
type DAST (Dynamic Application Security Testing)
Tests IHM (GUI testing) comme Seleniumn, QTP ou Cucumber
…
PIC : Plateforme agrégeant des outils permettant l’IC
Test immédiat des modifications
Notification rapide en cas de problèmes
Les problèmes d’intégration sont détectés et réparés de façon continue
réticences à la mise en oeuvre
difficultés de rédaction et de codage
couverture du code testé
temps nécessaire à la rédaction des cas de tests
véracité des cas de tests
temps nécessaire à la maintenance des cas de tests
les cas de tests doivent être répétables
complexité ⇒ base de données, fichiers
…
| JUnit 4 | La description |
|---|---|
@BeforeClass | La méthode est exécutée une fois avant le début de tous les tests. |
@AfterClass | La méthode est exécutée une fois tous les tests joués. |
@Ignore or @Ignore("Why disabled") | Marque que le test doit être désactivé. |
@Test (expected = Exception.class) | Échec si la méthode ne lance pas l’exception nommée. |
@Test(timeout=100) | Échoue si la méthode prend plus de 100 millisecondes. |
| JUnit 4 | La description |
|---|---|
import org.junit.* | Package pour l’utilisation des annotations. |
@Test | Identifie une méthode en tant que méthode de test. |
@Before | Exécuté avant chaque test (identifié par une méthode). |
@After | Exécuté après chaque test (chaque méthode). |
| Déclaration | La description |
|---|---|
fail([message]) | Laissez la méthode échouer. |
assertTrue([message,] boolean condition) | Vérifie que la condition booléenne est vraie. |
assertFalse([message,] boolean condition) | Vérifie que la condition booléenne est fausse. |
assertEquals([message,] expected, actual) | Teste que deux valeurs sont identiques. |
assertEquals([message,] expected, actual, tolerance) | Vérifiez que les valeurs float ou double correspondent. |
Besoin de plus de puissance ? utiliser AssertJ ou Hamcrest.
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.containsString;
...
assertThat("Hello world", containsString("world"));| Déclaration | La description |
|---|---|
assertNull([message,] object) | Vérifie que l’objet est nul. |
assertNotNull([message,] object) | Vérifie que l’objet n’est pas nul. |
assertSame([message,] expected, actual) | Vérifie que les deux variables se réfèrent au même objet. |
assertNotSame([message,] expected, actual) | Vérifie que les deux variables se réfèrent à des objets différents. |
une API pour écrire des tests
un mécanisme pour découvrir et lancer les tests
une API pour lancer les tests (pour les outils comme Eclipse)
Contrairement aux versions précédentes de JUnit, JUnit 5 est composé de plusieurs modules différents issus de trois sous-projets différents :
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
<!-- ... -->
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
<!-- ... -->
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<!-- ... -->
</plugins>
</build>
<!-- ... -->Le plugin Maven Surefire permet d’exécuter l’ensemble des tests unitaires d’un projet (méthodes annotées @Test).
Il recherchera les classes de tests dont les noms complets correspondent aux modèles suivants :
**/Test*.java
**/*Test.java
**/*Tests.java
**/*TestCase.java
Spring Boot 2.6 embarque directement JUnit5 par l’intermédiaire de sa dépendance spring-boot-starter-test.
Junit 4 :
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class BasicTest {
@Test
public void simpleTest1() {
LOGGER.info("--- Running test junit 4 -> 1 ---");
assertTrue(true);
}
}Junit 5 :
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class BasicTest {
@Test
void simpleTest1() {
LOGGER.info("--- Running test junit 5 -> 1 ---");
assertTrue(true);
}
}Note | Une classe / méthode de test Junit 5 peut être avec une visibilité private, non nécessairement public. |
Il est possible dans un projet d’exécuter des tests Junit 3/4 existants et d’y ajouter des tests Junit Jupiter.
<!-- ... -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2/version>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
<!-- ... -->Les annotations de base se trouvent dans le package org.junit.jupiter.api du module junit-jupiter-api.
Annotation | Description |
@Test | Indique qu’une méthode est une méthode de test. Contrairement à l’annotation @Test de JUnit 4, cette annotation ne déclare aucun attribut. |
@ParameterizedTest | Indique qu’une méthode est un test paramétré. |
@RepeatedTest | Indique qu’une méthode est un test template pour un test répété. |
@TestFactory | Indique qu’une méthode est un test factory pour des tests dynamiques. |
@TestTemplate | Indique qu’une méthode est un template de cas de test conçu pour être invoqué plusieurs fois en fonction du nombre de contextes d’appel. |
@TestClassOrder | Utilisé pour configurer l’ordre d’exécution de la classe de test pour les classes de test @Nested dans la classe de test annotée. |
@TestMethodOrder | Utilisé pour configurer l’ordre d’exécution de la méthode de test pour la classe de test annotée. similaire à @FixMethodOrder de JUnit 4. |
@TestInstance | Utilisé pour configurer le cycle de vie de l’instance de test pour la classe de test annotée. |
Annotation | Description |
@DisplayName | Déclare un nom d’affichage personnalisé pour la classe de test ou la méthode de test. |
@DisplayNameGeneration | Déclare un générateur de nom d’affichage personnalisé pour la classe de test. |
@BeforeEach | Indique que la méthode annotée doit être exécutée avant chaque méthode @Test, @RepeatedTest, @ParameterizedTest ou @TestFactory dans la classe actuelle. Analogue à @Before de JUnit 4. |
@AfterEach | Indique que la méthode annotée doit être exécutée après chaque méthode @Test, @RepeatedTest, @ParameterizedTest ou @TestFactory dans la classe actuelle. Analogue à @After de JUnit 4. |
@BeforeAll | Indique que la méthode annotée doit être exécutée avant toutes les méthodes @Test, @RepeatedTest, @ParameterizedTest et @TestFactory dans la classe actuelle. analogue à @AfterClass de JUnit 4. |
@AfterAll | Indique que la méthode annotée doit être exécutée après toutes les méthodes @Test, @RepeatedTest, @ParameterizedTest et @TestFactory dans la classe actuelle. analogue à @AfterClass de JUnit 4. |
Annotation | Description |
@Nested | Indique que la classe annotée est une classe de test imbriquée non statique. Les méthodes @BeforeAll et @AfterAll ne peuvent pas être utilisées directement dans une classe de test @Nested à moins que le cycle de vie de l’instance de test "par classe" ne soit utilisé. |
@Tag | Utilisé pour déclarer des tags pour les tests de filtrage, que ce soit au niveau de la classe ou de la méthode. analogue aux groupes de test dans TestNG ou aux catégories dans JUnit 4. |
@Disabled | Utilisé pour désactiver une classe de test ou une méthode de test. analogue à @Ignore de JUnit 4. |
@Timeout | Utilisé pour faire échouer un test, une fabrique de tests, un modèle de test ou une méthode de cycle de vie si son exécution dépasse une durée donnée. |
@ExtendWith | Utilisé pour enregistrer les extensions de manière déclarative. |
@RegisterExtension | Utilisé pour enregistrer des extensions de manière programmative. |
@TempDir | Utilisé pour fournir un répertoire temporaire via une injection de champ ou une injection de paramètres dans une méthode de cycle de vie ou une méthode de test. Situé dans le package org.junit.jupiter.api.io. |
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BasicTest {
public static final Logger LOGGER = LoggerFactory.getLogger(BasicTest.class);
@BeforeAll
static void initAll() {
LOGGER.info("--- Running once before first class test junit 5 ---");
}
@BeforeEach
public void init() {
LOGGER.info("--- Running before test junit 5 ---");
}
@Test
void succeedingTest() {
LOGGER.info("--- Running test junit 5 -> 1 ---");
assertTrue(true);
}
@Test
@Disabled("Test ne marche plus mais il faut livrer")
void skipFailingTest() {
LOGGER.info("--- Running test junit 5 -> 2 ---");
fail("Failing test");
}
@AfterEach
void tearDown() {
LOGGER.info("--- Running after test junit 5 ---");
}
@AfterAll
static void tearDownAll() {
LOGGER.info("--- Running once after last class test junit 5 ---");
}
}L’annotation @RepeatedTest permet de répéter plusieurs fois un même test.
Exemple :
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.TestInfo;
class RepeatedTestsDemo {
@BeforeEach
void beforeEach(TestInfo testInfo, RepetitionInfo repetitionInfo) {
int currentRepetition = repetitionInfo.getCurrentRepetition();
int totalRepetitions = repetitionInfo.getTotalRepetitions();
String methodName = testInfo.getTestMethod().get().getName();
logger.info(String.format("About to execute repetition %d of %d for %s", //
currentRepetition, totalRepetitions, methodName));
}
@RepeatedTest(10)
void repeatedTest() {
// ...
}
@RepeatedTest(5)
void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) {
assertEquals(5, repetitionInfo.getTotalRepetitions());
}
}
/*
├─ RepeatedTestsDemo ✔
│ ├─ repeatedTest() ✔
│ │ ├─ repetition 1 of 10 ✔
│ │ ├─ repetition 2 of 10 ✔
│ │ ├─ repetition 3 of 10 ✔
│ │ ├─ repetition 4 of 10 ✔
│ │ ├─ repetition 5 of 10 ✔
│ │ ├─ repetition 6 of 10 ✔
│ │ ├─ repetition 7 of 10 ✔
│ │ ├─ repetition 8 of 10 ✔
│ │ ├─ repetition 9 of 10 ✔
│ │ └─ repetition 10 of 10 ✔
│ ├─ repeatedTestWithRepetitionInfo(RepetitionInfo) ✔
│ │ ├─ repetition 1 of 5 ✔
│ │ ├─ repetition 2 of 5 ✔
│ │ ├─ repetition 3 of 5 ✔
│ │ ├─ repetition 4 of 5 ✔
│ │ └─ repetition 5 of 5 ✔
*/Cas d’usages limités : s’assurer que le résultat d’un traitement reste identique après n exécutions, qu’un traitement doit retourner une erreur au bout de n exécutions …
L’annotation @ParameterizedTest permet de répéter plusieurs fois un même test mais avec des paramètres différents (nécessite dépendance junit-jupiter-params).
Les valeurs de paramètres sont définis par l’intermédiaire d’une annotation @*Source, plusieurs techniques permettent de les alimenter.
Exemple simple d’une liste de paramètre String :
class ParameterizedTestDemo {
@ParameterizedTest
@NullSource
@EmptySource
@ValueSource(strings = {"String 1", "String 2", "\n"})
void shouldExecuteForStringList(String input) {
/* input = [ null, "", "String 1", "String 2", "\n"] */
}
}L’annotation @ValueSource accepte les types primitifs + java.lang.String et java.lang.Class, par exemple :
@ValueSource(ints = { 1, 2, 3 })L’annotation @EnumSource permet d’utiliser des constantes de type Enum, par exemple :
@ParameterizedTest
@EnumSource(value = Month.class, names = {"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER"})
void testEnumParam(Month param) {
LOGGER.info("Exécution test enum param, valeur : " + param);
assertNotNull(param);
}L’annotation @MethodSource permet d’exécuter une méthode statique pour générer une liste de paramètres, par exemple :
@ParameterizedTest
@MethodSource("stringProvider")
void testWithExplicitLocalMethodSource(String argument) {
assertNotNull(argument);
}
static Stream<String> stringProvider() {
return Stream.of("apple", "banana");
}
static Stream<Arguments> stringIntAndListProvider() {
return Stream.of(
arguments("apple", 1, Arrays.asList("a", "b")),
arguments("lemon", 2, Arrays.asList("x", "y"))
);
}L’annotation @CsvSource permet de charger des paramètres décrits sous forme csv, par exemple :
@ParameterizedTest
@CsvSource({
"apple, 1",
"banana, 2",
"lemon, 3",
"strawberry, 4"
})
void testWithCsvSource(String fruit, int rank) {
assertNotNull(fruit);
assertNotEquals(0, rank);
}L’annotation @CsvFileSource permet de charger des paramètres générés à partir d’un fichier csv, par exemple :
@ParameterizedTest
@CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSourceFromClasspath(String country, int reference) {
assertNotNull(country);
assertNotEquals(0, reference);
}
@ParameterizedTest
@CsvFileSource(files = "src/test/resources/two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSourceFromFile(String country, int reference) {
assertNotNull(country);
assertNotEquals(0, reference);
}L’annotation @TempDir permet l’utilisation d’un répertoire temporaire pour l’ensemble des tests d’une classe (Répertoire créé dans /tmp puis supprimé automatiquement à chaque test).
@Rule
public TemporaryFolder tmpFolder = new TemporaryFolder();ou
/* Erreur d'assertion si le temporary folder ne peut être supprimé */
public TemporaryFolder folder = TemporaryFolder.builder().assureDeletion().build();ou
@ClassRule
public static TemporaryFolder globalFolder = new TemporaryFolder();/* Répertoire tmp de classe */
@TempDir
static Path sharedTempDir;
/* Répertoire tmp de méthode test */
@TempDir
File tempDir;Contrairement aux différentes annotations d’extensions dans Junit 4 (@RunWith, @Rule, @ClassRule), le modèle d’extension JUnit Jupiter se compose d’un concept unique et cohérent : l’API Extension avec l’annotation @ExtendWith.
Exemple :
@ExtendWith(MockitoExtension.class)
@ExtendWith({ a.class, b.class })
public class ExtensionTest {
@Test
@ExtendWith(c.class)
void should_use_extensions() {
...
}
}@RunWith(SpringJUnit4ClassRunner.class) en JUnit 4 devient @ExtendWith(SpringExtension.class) en JUnit 5.
Il est possible à l’aide d’annotations d’extension Junit5 d’activer ou de désactiver l’exécution de tests en fonction du contexte :
Selon le système d’exploitation (@EnabledOnOs({ OS.LINUX, OS.WINDOWS}), @DisabledOnOs(OS.MAC) …)
Selon la version java (@EnabledOnJre(JRE.JAVA_8), @DisabledOnJre(JRE.JAVA_11) …)
Selon la valeur d’une propriété système (@EnabledIfSystemProperty(named = "java.vm.vendor", matches = "Oracle."),@DisabledIfSystemProperty(named = "os.version", matches = ".*10.") …)
Selon la valeur d’une variable d’environnement (@EnabledIfEnvironmentVariable(named = "ORACLE_HOME", matches = "/opt/oracle/product/19c/.*") …)
Selon une ou des conditions custom : créer une classe implémentant org.junit.jupiter.api.extension.ExecutionCondition
L’utilisation de l’interface ParameterResolver permet d’injecter un paramètre dans une méthode de test.
public class MyCustomParameterResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) throws ParameterResolutionException {
// Retourne true si le type de l'objet paramètre est correct
...
return parameterContext.getParameter().getType() == MyCustomParameterType.class;
}
@Override
public Object resolveParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) throws ParameterResolutionException {
//Retourne l'instance d'un objet à utiliser en paramètre
MyCustomParameterType customParam = new MyCustomParameterType();
...
return customParam;
}
}
@ExtendWith(MyCustomParameterResolver.class)
public class CustonParameterResolverTest {
private MyCustomParameterType customParamGlobal;
public CustonParameterResolverTest(MyCustonParameterType customParam) {
this.customParamGlobal = customParam;
}
@Test
void test(MyCustonParameterType customParam) {
...
}JUnit Jupiter conserve de nombreuses méthodes d’assertion de JUnit 4 et en ajoute quelques-unes qui se prêtent bien à une utilisation avec les lambdas Java 8. Toutes les assertions JUnit Jupiter sont des méthodes statiques de la classe org.junit.jupiter.api.Assertions.
Elles permettent d’exécuter un ensemble complet d’assertions même en cas d’erreur
assertAll("Should check user admin identity",
() -> assertEquals("admin", user.getLastName()),
() -> assertEquals("admin", user.getFirstName()),
() -> assertTrue(user.isAdmin())
);assertAll("person",
() -> {
String firstName = person.getFirstName();
assertNotNull(firstName);
// Executed only if the previous assertion is valid.
assertAll("first name",
() -> assertTrue(firstName.startsWith("J")),
() -> assertTrue(firstName.endsWith("e"))
);
},
() -> {
// Grouped assertion, so processed independently
// of results of first name assertions.
String lastName = person.getLastName();
assertNotNull(lastName);
// Executed only if the previous assertion is valid.
assertAll("last name",
() -> assertTrue(lastName.startsWith("D")),
() -> assertTrue(lastName.endsWith("e"))
);
}
);Comment donner du sens à vos tests unitaires ?
En appliquant certains principes du Behavior Driven Development (BDD)
Pourquoi ?
Afin d’obtenir une classe de tests unitaires claire et maintenable.
Les tests doivent être
compréhensibles, lisibles et facilement modifiables
automatisables, répétables et exécutés rapidement
Mockito est un framework Java, permettant :
de mocker ou espionner des objets,
simuler et vérifier des comportements,
ou encore simplifier l’écriture de tests unitaires.
Exemple dans une Architecture Hexagonale sur les principes du Domain Driven Development (DDD) :
L’approche DDD vise, à isoler un domaine métier avec les caractéristiques suivantes:
Approfondissement des règles métier spécifiques en accord avec le modèle d’entreprise, la stratégie et les processus métier.
Isolation des autres domaines métier et des autres couches de l’architecture de l’application.
Modèle construit avec un couplage faible avec les autres couches de l’application.
Facilement maintenable, testable et versionnable.
Modèle conçu avec le moins de dépendances possibles avec une technologie ou un framework.
« Permettre à une application d’être pilotée aussi bien par des utilisateurs que par des programmes, des tests automatisés ou des scripts batchs, et d’être développée et testée en isolation de ses éventuels systèmes d’exécution et bases de données. »
Alistair Cockburn en 2005 (hexagonal architecture).
L’architecture hexagonale repose sur trois principes et techniques:
Séparer explicitement la logique métier de la partie exposition (client-side) et persistence (server-side).
Les dépendances partent des couches techniques (client-side / server-side) vers la couche logique métier
Il faut isoler les couches en utilisant des ports et des adaptateurs
En effet, il sera très intuitif d’écrire son test en suivant la notion //Given //When //Then, et nous verrons que Mockito met l’accent sur la 1ère et la 3ème notion.
Deux possibilités :
Ajouter l’annotation @RunWith comme suit :
@RunWith(MockitoJunitRunner.class)
public class MyTestClass {
}Ou à l’initialisation dans la méthode d’initialisation (ici setUp())
private AutoCloseable closeable;
...
@Before
public void setUp() {
closeable = MockitoAnnotations.openMocks(this);
}
@After
public void tearDown() throws Exception {
closeable.close();
}Il est conseillé de libérer la ressource après chaque test (voir méthode tearDown()).
Mockito est capable de « stubber » (bouchonner) des classes concrètes mais aussi des interfaces.
On peut appeler la méthode mock(…) sur une classe :
User user = Mockito.mock(User.class);
Ou placer une annotation si la variable est en instance de classe
@Mock User user;
Retour d’une valeur unique
Mockito.when(user.getLogin()).thenReturn(‘user1’);
Faire appel à la méthode d’origine
Mockito.when(user.getLogin()).thenCallRealMethod();
Levée d’exceptions
Mockito.when(user.getLogin()).thenThrow(new RuntimeException());
@SpyLa différence entre @Mock et @Spy réside dans le fait que la deuxième permet d’instancier l’objet mocké; on peut ainsi effectuer un mock partiel.
Quand on appelle une méthode de l’objet « espionné »
la vraie méthode est appelée,
à moins qu’un comportement ai été défini.
@Spy User user = new User(‘user1’); user.getLogin() // retourne user1 Mockito.when(user.getPassword()).thenReturn(‘top secret’);
verify(user).getLogin(); // le test passe si getLogin() est appelée avant la fin du timeout (ici 100 ms) verify(user, timeout(100)).getLogin(); // le test passe si il n'existe aucune autre interaction sur le mock (non vérifiée) verifyNoMoreInteractions(luser);
Mockito permet également d’injecter des ressources (classes nécessaires au fonctionnement de l’objet mocké), en utilisant l’annotation @InjectMock.
L’injection des mocks dans l’objet marqué par @InjectMock se fera (par ordre de priorité) :
injection par le constructeur
injection par la méthode de type « setter »
injection par l’attribut (même si celui-ci est private)
ouvrir le pdf tp/tp-mocks/tp-mocks.pdf
C’est à vous ;)